package middleware

import (
	"bytes"
	"encoding/json"
	"github.com/google/uuid"
	log2 "github.com/starmotioncf/common/log"
	"github.com/starmotioncf/soutchatinternal"
	"github.com/starmotioncf/soutchatinternal/constant"
	"io"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

func RequestLogMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		//生成一个携带traceId的logger放在ctx
		traceId := ctx.Request.Header.Get(constant.HeaderXTraceId)
		if traceId == "" {
			traceId = uuid.New().String()
		}
		ctx.Set(log2.CtxLogger, log2.Log().With(zap.String("traceId", traceId)))

		//开始记录请求日志
		st := time.Now()
		var reqBody []byte
		//上传文件的不记录
		if ctx.Request.Body != nil && !strings.HasPrefix(ctx.Request.Header.Get("Content-Type"), "multipart/form-data") {
			reqBody, _ = ctx.GetRawData()
			ctx.Request.Body = io.NopCloser(bytes.NewBuffer(reqBody))
		}

		blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: ctx.Writer}
		ctx.Writer = blw

		zapFields := make([]zap.Field, 0, 7)
		zapFields = append(zapFields, zap.String("method", ctx.Request.Method))
		zapFields = append(zapFields, zap.String("url", ctx.Request.URL.String()))
		zapFields = append(zapFields, zap.Any("header", getHeaderLog(ctx)))
		zapFields = append(zapFields, zap.ByteString("req", reqBody))
		ctx.Next()
		zapFields = append(zapFields, zap.Uint64("uid", internal.GetUidFromCtx(ctx)))
		zapFields = append(zapFields, zap.String("rgn", internal.GetRegionFromCtx(ctx).Code()))
		zapFields = append(zapFields, zap.Int("status", ctx.Writer.Status()))
		zapFields = append(zapFields, zap.Any("res", toBody(blw.body.Bytes())))
		zapFields = append(zapFields, zap.Int64("rt", time.Now().Sub(st).Milliseconds()))
		log2.WithContext(ctx).Info("request", zapFields...)
	}
}

type bodyLogWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.body.Write(b)
	return w.ResponseWriter.Write(b)
}

func toBody(b []byte) any {
	res := make(map[string]interface{})
	if err := json.Unmarshal(b, &res); err != nil {
		return string(b)
	}
	return res
}

func getHeaderLog(ctx *gin.Context) map[string]any {
	return map[string]any{
		"token": strings.TrimLeft(ctx.Request.Header.Get("Authorization"), "Bearer "),
		"cc":    ctx.Request.Header.Get(constant.CFIPCountry),
		"mac":   ctx.Request.Header.Get(constant.HeaderXMac),
		"ip":    ctx.ClientIP(),
	}
}
